ns('J3D').Matrix = 
  (function() {
     var f = J3D.F;
     var m = {
       createVec3: function(x, y, z) {
         return f.list(x, y, z);
       },
       
       addVec3: function(vec31, vec32) {
         return f.combine(f.plus)(vec31)(vec32);
       },
     
       subtractVec3: function(vec31, vec32) {
         return f.combine(f.subtrace)(vec31)(vec32);
       },
     
       multiplyVec3: function(v31, v32) {
         return f.combine(f.multiply)(v31)(v32);
       },
     
       negateVec3: f.map(function(el) { return -el; }),
     
       scaleVec3: function(v, s) {
         return f.map(function(el) { return el * s; })(v);
       },
       
       /*
       dotProduct: function(v1, v2) {
         return f.compose(f.foldr(f.plus(0), f.map(f.multiply)));
       },
        */
       
       dotVec3: function(v) {
         return f.compose(f.foldr(f.plus)(0), f.map(f.square))(v);
       },
     
       absoluteVec3: function(v) {
         return Math.sqrt(m.dotVec3(v));
       },
     
       normalizeVec3: function(v) {
         return m.scaleVec3(v, 1 / m.absoluteVec3(v));
       },
       
       directionVec3: function(v1, v2) {
         return m.normalizeVec3(m.subtractVec3(v1, v2));
       },
       
       lerpVec3: function(v1, v2, lerp) {
         return f.combine(
           function(e1) {
             return function(e2) {
               return e1 + lerp * (e1 - e2);
             };
           })(v1)(v2);
       },
       
       distVec3: function(v1, v2) {
         return m.absoluteVec3(m.subtractVec3(v1, v2));
       },
       
       createMat3: function() {
         var a = arguments;
         return f.list(
           f.list(a[0], a[1], a[2]),
           f.list(a[3], a[4], a[5]),
           f.list(a[6], a[7], a[8]));
       },
       
       identityMat3: function() {
         return f.list(
           f.list(1, 0, 0),
           f.list(0, 1, 0),
           f.list(0, 0, 1));
       },
       
       createMat4: function() {
         var a = arguments;
         return f.list(
           f.list(a[0], a[1], a[2], a[3]),
           f.list(a[4], a[5], a[6], a[7]),
           f.list(a[8], a[9], a[10], a[11]),
           f.list(a[12], a[13], a[14], a[15]));
       },
       
       getPitch: function(angle) {
         var cos = Math.cos(angle);
         var sin = Math.sin(angle);
         return m.createMat4(
           cos, 0, sin, 0,
           sin, cos, 0, 0,
           0, 0, 1, 0,
           0, 0, 0, 1);
       },
       
       getYaw: function(angle) {
         var cos = Math.cos(angle);
         var sin = Math.sin(angle);
         return m.createMat4(
           cos, -sin, 0, 0,
           sin, cos, 0, 0,
           0, 0, 1, 0,
           0, 0, 0, 1);
       },
       
       equalMatrix: function(m1, m2) {
         return f.foldr(f.and)(true)(f.combine(function(e1) {
           return function(e2) {
             return f.listEqual(e1, e2);
           };
         })(m1)(m2));         
       },
       
       getRoll: function(angle) {
         var sin = Math.sin(angle);
         var cos = Math.cos(angle);
         return m.createMat4(
           1, 0, 0, 0,
           0, cos, sin, 0,
           0, sin, cos, 0,
           0, 0, 0, 1);
       },
       
       identityMat4: function() {
         return m.createMat4(
           1, 0, 0, 0,
           0, 1, 0, 0,
           0, 0, 1, 0,
           0, 0, 0, 1);
       },
       
       transposeMat4: function(m) {
         return f.foldr(f.combine(f.pair))(f.list(null, null, null, null))(m);
       },
       
       multiplyMatVec: function(m, v) {
         return f.map(f.compose(
           f.foldr(f.plus)(0), 
           f.combine(f.multiply)(v)))(m);
       },
       
       multiplyMat4: function(m1, m2) {
         return f.map(
           function(l1) {
             return f.map(
               f.compose(f.foldr(f.plus)(0), f.combine(f.multiply)(l1))
             )(m.transposeMat4(m2));
           })(m1);
       },
       
       frustum: function(left, right, bottom, top, near, far) {
         var rl = right - left, tb = top - bottom, fn = far - near;
         return m.createMat4(
           near * 2 / rl, 0, 0, 0,
           0, near * 2 /tb, 0, 0,
           (right + left) / rl, (top + bottom) / tb, -(far + near) / fn, -1,
           0, 0, -(far * near * 2) / fn, 0
         );
       },
       
       perspective: function(fovy, aspect, near, far) {
         var top = near * Math.tan(fovy * Math.PI / 360), right = top * aspect;
         return m.frustum(-right, right, -top, top, near, far);
       }
     };
     return m;
   })();
